/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.policy.compressor;

import org.mmtk.utility.Log;
import org.mmtk.utility.alloc.BumpPointer;
import org.mmtk.utility.alloc.LinearScan;
import org.mmtk.vm.VM;

import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Extent;
import org.vmmagic.unboxed.ObjectReference;


/**
 * This class implements unsynchronized (local) elements of a
 * copying collector. Allocation is via the bump pointer
 * (@see BumpPointer).
 *
 * @see BumpPointer
 */
@Uninterruptible public final class MarkCopyLocal extends BumpPointer {

  private static boolean VERBOSE = true;
  
  /**
   * Constructor
   *
   * @param space The space to bump point into.
   */
  public MarkCopyLocal(MarkCopySpace space) {
    super(space, true);
  }

  /**
   * Constructor
   */
  public MarkCopyLocal() {
    super(null, true);
  }
  
  /**
   * 
   * @return The initial region of the BumpPointer
   */
  public Address getInitialRegion() {
    return initialRegion;
  }
  
  public Address getLastRegion() {
    return region;
  }
  
  /**
   * Prepare for collection: update the metadata for the current region, and flush
   * this bump-pointer's allocations to the global page list.
   */
  public void prepare() {
    setDataEnd();

  }
  
  /**
   * Sets cursor as data end on current region, if current region is zero does nothing
   */
  public void setDataEnd() {
    if (!region.isZero()) {
      setDataEnd(region, cursor);
    }
  }
  
  public int getTotalLiveData() {
    Address lastRegion = getLastRegion();
    int offset = CompressorMetadata.getOffsetVectorCellAddress(lastRegion).loadInt();
    return offset + Block.liveData(lastRegion);
  }
  
  public static int getTotalLiveData(Address bumpStart) {
    Address region = bumpStart;
    Address prevReg = region;
    
    // Find the last region in the BumpPointer
    do {
      prevReg = region;
      region = getNextRegion(region);
    } while(!region.isZero());
    
    // Find the offset value in the last region
    int lastRegOffset = CompressorMetadata.getOffsetVectorCellAddress(prevReg).loadInt();
    int lastRegLiveData = Block.liveData(prevReg);
    
    if(VERBOSE) {
      Log.write("[getTotalLiveData] bumpStart:"); Log.write(bumpStart); Log.writeln();
      Log.write("[getTotalLiveData] last region: "); Log.write(prevReg); Log.writeln();
      Log.write("[getTotalLiveData] last region offsetVector "); Log.write(CompressorMetadata.getOffsetVectorCellAddress(prevReg)); Log.writeln();
      Log.write("[getTotalLiveData] last region offset "); Log.write(lastRegOffset); Log.writeln();
      Log.write("[getTotalLiveData] last region live data "); Log.write(lastRegLiveData); Log.writeln();
    }
    
    // Calculate live data in the last block and return the total live data
    return lastRegOffset + lastRegLiveData;
  }
  
  public Address prealloc(Extent byteSize) {
    // TODO
    int align = MAX_ALIGNMENT;
    int offset = 0;

    return alloc(byteSize.toInt(), align, offset);
  }

  /**
   * Same as BumpPointer.scanRegion but does not use cursor. 
   * @pre start.plus(DATA_END_OFFSET).loadAddress().NE(Address.zero())
   * @param scanner The scan object to delegate to.
   * @param start The start of this region
   */
  @Inline
  protected static void scanRegionNoCursor(LinearScan scanner, Address start) {
    if (VM.VERIFY_ASSERTIONS) {
      VM.assertions._assert(isRegionAligned(start));
    }
    
    /* Get the end of this region */
    Address dataEnd = start.plus(DATA_END_OFFSET).loadAddress();
    
    if(VM.VERIFY_ASSERTIONS) {
      VM.assertions._assert(dataEnd.NE(Address.zero()));
    }
    
    Address currentLimit = dataEnd;
    
    if(VERBOSE) {
      Log.write("dataEnd=currentLimit="); Log.write(currentLimit); Log.writeln();
    }
    if (currentLimit.EQ(start.plus(DATA_END_OFFSET).plus(BYTES_IN_ADDRESS))) {
      /* Empty region, so we can not call getObjectFromStartAddress() */
      return;
    }

    ObjectReference current = VM.objectModel.getObjectFromStartAddress(start.plus(DATA_START_OFFSET));

    /* Loop through each object up to the limit */
    do {
      /* Read end address first, as scan may be destructive */
      Address currentObjectEnd = VM.objectModel.getObjectEndAddress(current);
      scanner.scan(current);

      if (currentObjectEnd.GE(currentLimit)) {
        /* We have scanned the last object */
        break;
      }
      /* Find the next object from the start address (dealing with alignment gaps, etc.) */
      ObjectReference next = VM.objectModel.getObjectFromStartAddress(currentObjectEnd);

      if (VM.VERIFY_ASSERTIONS) {
        /* Must be monotonically increasing */
        VM.assertions._assert(next.toAddress().GT(current.toAddress()));
      }
      current = next;
    } while (true);
  }
  
  /**
   * Same as BumpPointer.linearScan, but starts scanning from regionStart, 
   * instead of initialRegion
   * 
   * @param scanner The scan object to delegate scanning to.
   * @param regionStart the region to start scanning from
   */
  public static void linearScan(LinearScan scanner, Address regionStart) {
    if (VM.VERIFY_ASSERTIONS) {
      //VM.assertions._assert(allowScanning);
      VM.assertions._assert(isRegionAligned(regionStart));
    }
    
    /* Has this allocator ever allocated anything? */
    if (regionStart.isZero()) {
      if(VERBOSE) {
        Log.write("Allocator never allocated anything"); Log.writeln();
      }
      return;
    }
    
    if(VERBOSE) {
      Log.write("Looping through regions");  Log.writeln();
    }
    
    /* Loop through active regions or until the last region */
    Address start = regionStart;
    while (!start.isZero()) {
      if(VERBOSE) {
        Log.write("Starting scan of region "); Log.write(start); Log.writeln();
      }
      scanRegionNoCursor(scanner, start); // Scan this region
      start = getNextRegion(start); // Move on to next
      if (VERBOSE) {
        Log.write("Next region: "); Log.write(start); Log.writeln();
      }
    }
  }
  
  /**
   * Perform a linear scan through the objects allocated by this bump pointer.
   *
   * @param scanner The scan object to delegate scanning to.
   */
  @Inline
  public void linearScan(LinearScan scanner) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(allowScanning);
    /* Has this allocator ever allocated anything? */
    if (initialRegion.isZero()) {
      if(VERBOSE) {
        Log.write("Allocator never allocated anything"); Log.writeln();
      }
      return;
    }
    
    if(VERBOSE) {
      Log.write("Looping through regions");  Log.writeln();
    }
    
    /* Loop through active regions or until the last region */
    Address start = initialRegion;
    while (!start.isZero()) {
      ((MetadataComputingScanner)scanner).newRegion(start);
      scanRegion(scanner, start); // Scan this region
      start = getNextRegion(start); // Move on to next
    }
  }
  
  /**
   * Maximum size of a single region. Important for children that implement
   * load balancing or increments based on region size.
   * @return the maximum region size
   */
  @Override
  protected Extent maximumRegionSize() { 
    return Extent.fromIntZeroExtend(1 << LOG_BLOCK_SIZE); 
  }

  public void setInitialRegion(Address head) {
    initialRegion = head;
    Address curr = head;
    Address prev = Address.zero();
    while(!curr.isZero()) {
      prev = curr;
      curr = getNextRegion(curr);
    }
    region = prev;
    cursor = getDataEnd(prev);
    
    // TODO: check if this is correct
    // Update limit to have the correct region limit
    if (VERBOSE) {
      Log.write("Setting initial region: "); Log.writeln();
      Log.write("\t"); Log.write("region "); Log.write(region); Log.writeln();
      Log.write("\t"); Log.write("cursor "); Log.write(cursor); Log.writeln();
      Log.write("\t"); Log.write("getRegionLimit(region) "); Log.write(getRegionLimit(region)); Log.writeln();
    }
    updateLimit(getRegionLimit(region), region, 0);
  }

  
}
